/* Copyright 2007 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */

/*
 * Useful books and links:
 * http://docs.sun.com/source/806-3568/ncg_goldberg.html#689 - What every computer scientist should know about floating-point arithmetic
 */

/*
 * Provide methods that compare two doubles / floats and inform the user if addition / subtraction / multiplication / division would lead to a loss of precision
 */
using System;
using dnAnalytics.Math;
using NUnit.Framework;

namespace dnAnalytics.Tests.Math
{
    /*
     * There is some irony in the fact that we're testing a floating point comparison class with the incorrect method 
     * that was the reason for writing the floating point comparison class in the first place.
     */

    [TestFixture]
    public sealed class PrecisionTests
    {
        private const double mAcceptableError = 1e-12;
        private readonly double mDoublePrecision = System.Math.Pow(2, -53);

        [Test]
        public void Magnitude()
        {
            Assert.AreEqual(0, Precision.Magnitude(0));

            Assert.AreEqual(0, Precision.Magnitude(1));
            Assert.AreEqual(1, Precision.Magnitude(10));
            Assert.AreEqual(2, Precision.Magnitude(100));
            Assert.AreEqual(3, Precision.Magnitude(1000));
            Assert.AreEqual(4, Precision.Magnitude(10000));
            Assert.AreEqual(5, Precision.Magnitude(100000));
            Assert.AreEqual(6, Precision.Magnitude(1000000));

            Assert.AreEqual(7, Precision.Magnitude(1e7));
            Assert.AreEqual(8, Precision.Magnitude(1e8));
            Assert.AreEqual(9, Precision.Magnitude(1e9));
            Assert.AreEqual(10, Precision.Magnitude(1e10));
            Assert.AreEqual(11, Precision.Magnitude(1e11));
            Assert.AreEqual(12, Precision.Magnitude(1e12));

            Assert.AreEqual(5, Precision.Magnitude(1.1e5));
            Assert.AreEqual(-5, Precision.Magnitude(2.2e-5));
            Assert.AreEqual(9, Precision.Magnitude(3.3e9));
            Assert.AreEqual(-11, Precision.Magnitude(4.4e-11));
        }

        // Bug fix test: Magnitude of negative numbers returns zero
        [Test]
        public void MagnitudeWithNegativeValues()
        {
            Assert.AreEqual(0, Precision.Magnitude(-1));
            Assert.AreEqual(1, Precision.Magnitude(-10));
            Assert.AreEqual(2, Precision.Magnitude(-100));
            Assert.AreEqual(3, Precision.Magnitude(-1000));
            Assert.AreEqual(4, Precision.Magnitude(-10000));
            Assert.AreEqual(5, Precision.Magnitude(-100000));
            Assert.AreEqual(6, Precision.Magnitude(-1000000));

            Assert.AreEqual(7, Precision.Magnitude(-1e7));
            Assert.AreEqual(8, Precision.Magnitude(-1e8));
            Assert.AreEqual(9, Precision.Magnitude(-1e9));
            Assert.AreEqual(10, Precision.Magnitude(-1e10));
            Assert.AreEqual(11, Precision.Magnitude(-1e11));
            Assert.AreEqual(12, Precision.Magnitude(-1e12));

            Assert.AreEqual(5, Precision.Magnitude(-1.1e5));
            Assert.AreEqual(-5, Precision.Magnitude(-2.2e-5));
            Assert.AreEqual(9, Precision.Magnitude(-3.3e9));
            Assert.AreEqual(-11, Precision.Magnitude(-4.4e-11));
        }

        [Test]
        public void Value()
        {
            Assert.AreEqual(0, Precision.Value(0), mAcceptableError);

            Assert.AreEqual(1, Precision.Value(1), mAcceptableError);
            Assert.AreEqual(1, Precision.Value(10), mAcceptableError);
            Assert.AreEqual(1, Precision.Value(100), mAcceptableError);
            Assert.AreEqual(1, Precision.Value(1000), mAcceptableError);
            Assert.AreEqual(1, Precision.Value(10000), mAcceptableError);
            Assert.AreEqual(1, Precision.Value(100000), mAcceptableError);
            Assert.AreEqual(1, Precision.Value(1000000), mAcceptableError);

            Assert.AreEqual(1.1, Precision.Value(1.1e5), mAcceptableError);
            Assert.AreEqual(2.2, Precision.Value(2.2e-5), mAcceptableError);
            Assert.AreEqual(3.3, Precision.Value(3.3e9), mAcceptableError);
            Assert.AreEqual(4.4, Precision.Value(4.4e-11), mAcceptableError);
        }

        // Bug fix test: Magnitude of negative numbers returns zero
        [Test]
        public void ValueWithNegativeValues()
        {
            Assert.AreEqual(0, Precision.Value(0), mAcceptableError);

            Assert.AreEqual(-1, Precision.Value(-1), mAcceptableError);
            Assert.AreEqual(-1, Precision.Value(-10), mAcceptableError);
            Assert.AreEqual(-1, Precision.Value(-100), mAcceptableError);
            Assert.AreEqual(-1, Precision.Value(-1000), mAcceptableError);
            Assert.AreEqual(-1, Precision.Value(-10000), mAcceptableError);
            Assert.AreEqual(-1, Precision.Value(-100000), mAcceptableError);
            Assert.AreEqual(-1, Precision.Value(-1000000), mAcceptableError);

            Assert.AreEqual(-1.1, Precision.Value(-1.1e5), mAcceptableError);
            Assert.AreEqual(-2.2, Precision.Value(-2.2e-5), mAcceptableError);
            Assert.AreEqual(-3.3, Precision.Value(-3.3e9), mAcceptableError);
            Assert.AreEqual(-4.4, Precision.Value(-4.4e-11), mAcceptableError);
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void RangeOfMatchingFloatingPointNumbersWithNegativeUlps()
        {
            double max;
            double min;
            Precision.RangeOfMatchingFloatingPointNumbers(10, -1, out min, out max);
        }

        [Test]
        public void RangeOfMatchingFloatingPointNumbersWithValueAtInfinity()
        {
            double max;
            double min;

            Precision.RangeOfMatchingFloatingPointNumbers(double.PositiveInfinity, 1, out min, out max);
            Assert.AreEqual(double.PositiveInfinity, min);
            Assert.AreEqual(double.PositiveInfinity, max);

            Precision.RangeOfMatchingFloatingPointNumbers(double.NegativeInfinity, 1, out min, out max);
            Assert.AreEqual(double.NegativeInfinity, min);
            Assert.AreEqual(double.NegativeInfinity, max);
        }

        [Test]
        public void RangeOfMatchingFloatingPointNumbersWithValueAtNaN()
        {
            double max;
            double min;

            Precision.RangeOfMatchingFloatingPointNumbers(double.NaN, 1, out min, out max);
            Assert.IsTrue(double.IsNaN(min));
            Assert.IsTrue(double.IsNaN(max));
        }

        // Numbers are calculated with the UlpsCalculator program which can be found in UlpsCalculator.cs
        [TestCase(0, -10 * double.Epsilon, 10 * double.Epsilon)]
        [TestCase(1.0, 0.99999999999999889, 1.0000000000000022)]
        [TestCase(2.0, 1.9999999999999978, 2.0000000000000044)]
        [TestCase(3.0, 2.9999999999999956, 3.0000000000000044)]
        [TestCase(4.0, 3.9999999999999956, 4.0000000000000089)]
        [TestCase(5.0, 4.9999999999999911, 5.0000000000000089)]
        [TestCase(6.0, 5.9999999999999911, 6.0000000000000089)]
        [TestCase(7.0, 6.9999999999999911, 7.0000000000000089)]
        [TestCase(8.0, 7.9999999999999911, 8.0000000000000178)]
        public void RangeOfMatchingFloatingPointNumbersWithPositiveValue(double input, double expectedMin, double expectedMax)
        {
            double max;
            double min;

            Precision.RangeOfMatchingFloatingPointNumbers(input, 10, out min, out max);
            Assert.AreEqual(expectedMin, min);
            Assert.AreEqual(expectedMax, max);
        }

        // Numbers are calculated with the UlpsCalculator program which can be found in UlpsCalculator.cs
        [TestCase(0, -10 * double.Epsilon, 10 * double.Epsilon)]
        [TestCase(-1.0, -1.0000000000000022, -0.99999999999999889)]
        [TestCase(-2.0, -2.0000000000000044, -1.9999999999999978)]
        [TestCase(-3.0, -3.0000000000000044, -2.9999999999999956)]
        [TestCase(-4.0, -4.0000000000000089, -3.9999999999999956)]
        [TestCase(-5.0, -5.0000000000000089, -4.9999999999999911)]
        [TestCase(-6.0, -6.0000000000000089, -5.9999999999999911)]
        [TestCase(-7.0, -7.0000000000000089, -6.9999999999999911)]
        [TestCase(-8.0, -8.0000000000000178, -7.9999999999999911)]
        public void RangeOfMatchingFloatingPointNumbersWithNegativeValue(double input, double expectedMin, double expectedMax)
        {
            double max;
            double min;

            Precision.RangeOfMatchingFloatingPointNumbers(input, 10, out min, out max);
            Assert.AreEqual(expectedMin, min);
            Assert.AreEqual(expectedMax, max);
        }

        [TestCase(0, 10 * double.Epsilon)]
        [TestCase(1.0, 1.0000000000000022)]
        [TestCase(2.0, 2.0000000000000044)]
        [TestCase(3.0, 3.0000000000000044)]
        [TestCase(4.0, 4.0000000000000089)]
        [TestCase(5.0, 5.0000000000000089)]
        [TestCase(6.0, 6.0000000000000089)]
        [TestCase(7.0, 7.0000000000000089)]
        [TestCase(8.0, 8.0000000000000178)]
        public void MaximumMatchingFloatingPointNumberWithPositiveValue(double input, double expectedMax)
        {
            double max = Precision.MaximumMatchingFloatingPointNumber(input, 10);
            Assert.AreEqual(expectedMax, max);
        }

        [TestCase(0, 10 * double.Epsilon)]
        [TestCase(-1.0, -0.99999999999999889)]
        [TestCase(-2.0, -1.9999999999999978)]
        [TestCase(-3.0, -2.9999999999999956)]
        [TestCase(-4.0, -3.9999999999999956)]
        [TestCase(-5.0, -4.9999999999999911)]
        [TestCase(-6.0, -5.9999999999999911)]
        [TestCase(-7.0, -6.9999999999999911)]
        [TestCase(-8.0, -7.9999999999999911)]
        public void MaximumMatchingFloatingPointNumberWithNegativeValue(double input, double expectedMax)
        {
            double max = Precision.MaximumMatchingFloatingPointNumber(input, 10);
            Assert.AreEqual(expectedMax, max);
        }

        [TestCase(0, -10 * double.Epsilon)]
        [TestCase(1.0, 0.99999999999999889)]
        [TestCase(2.0, 1.9999999999999978)]
        [TestCase(3.0, 2.9999999999999956)]
        [TestCase(4.0, 3.9999999999999956)]
        [TestCase(5.0, 4.9999999999999911)]
        [TestCase(6.0, 5.9999999999999911)]
        [TestCase(7.0, 6.9999999999999911)]
        [TestCase(8.0, 7.9999999999999911)]
        public void MinimumMatchingFloatingPointNumberWithPositiveValue(double input, double expectedMin)
        {
            double min = Precision.MinimumMatchingFloatingPointNumber(input, 10);
            Assert.AreEqual(expectedMin, min);
        }

        [TestCase(0, -10 * double.Epsilon)]
        [TestCase(-1.0, -1.0000000000000022)]
        [TestCase(-2.0, -2.0000000000000044)]
        [TestCase(-3.0, -3.0000000000000044)]
        [TestCase(-4.0, -4.0000000000000089)]
        [TestCase(-5.0, -5.0000000000000089)]
        [TestCase(-6.0, -6.0000000000000089)]
        [TestCase(-7.0, -7.0000000000000089)]
        [TestCase(-8.0, -8.0000000000000178)]
        public void MinimumMatchingFloatingPointNumberWithNegativeValue(double input, double expectedMin)
        {
            double min = Precision.MinimumMatchingFloatingPointNumber(input, 10);
            Assert.AreEqual(expectedMin, min);
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void RangeOfMatchingUlpsWithNegativeRelativeDifference()
        {
            long min;
            long max;
            Precision.RangeOfMatchingUlps(1, -1, out min, out max);
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void RangeOfMatchingUlpsWithValueAtInfinity()
        {
            long min;
            long max;
            Precision.RangeOfMatchingUlps(double.PositiveInfinity, -1, out min, out max);
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void RangeOfMatchingUlpsWithValueAtNaN()
        {
            long min;
            long max;
            Precision.RangeOfMatchingUlps(double.NaN, -1, out min, out max);
        }

        [TestCase(0, 10 * double.Epsilon, 10, 10)]
        [TestCase(1.0, (1.0000000000000022 - 1.0) / 1.0, 20, 10)]
        [TestCase(2.0, (2.0000000000000044 - 2.0) / 2.0, 20, 10)]
        [TestCase(3.0, (3.0000000000000044 - 3.0) / 3.0, 10, 10)]
        [TestCase(4.0, (4.0000000000000089 - 4.0) / 4.0, 20, 10)]
        [TestCase(5.0, (5.0000000000000089 - 5.0) / 5.0, 10, 10)]
        [TestCase(6.0, (6.0000000000000089 - 6.0) / 6.0, 10, 10)]
        [TestCase(7.0, (7.0000000000000089 - 7.0) / 7.0, 10, 10)]
        [TestCase(8.0, (8.0000000000000178 - 8.0) / 8.0, 20, 10)]
        public void RangeOfMatchingUlpsWithPositiveValue(double input, double relativeDifference, long expectedMin, long expectedMax)
        {
            long min;
            long max;

            Precision.RangeOfMatchingUlps(input, relativeDifference, out min, out max);
            Assert.AreEqual(expectedMin, min);
            Assert.AreEqual(expectedMax, max);
        }

        [TestCase(0, 10 * double.Epsilon, 10, 10)]
        [TestCase(-1.0, (1.0000000000000022 - 1.0) / 1.0, 10, 20)]
        [TestCase(-2.0, (2.0000000000000044 - 2.0) / 2.0, 10, 20)]
        [TestCase(-3.0, (3.0000000000000044 - 3.0) / 3.0, 10, 10)]
        [TestCase(-4.0, (4.0000000000000089 - 4.0) / 4.0, 10, 20)]
        [TestCase(-5.0, (5.0000000000000089 - 5.0) / 5.0, 10, 10)]
        [TestCase(-6.0, (6.0000000000000089 - 6.0) / 6.0, 10, 10)]
        [TestCase(-7.0, (7.0000000000000089 - 7.0) / 7.0, 10, 10)]
        [TestCase(-8.0, (8.0000000000000178 - 8.0) / 8.0, 10, 20)]
        public void RangeOfMatchingUlpsWithNegativeValue(double input, double relativeDifference, long expectedMin, long expectedMax)
        {
            long min;
            long max;

            Precision.RangeOfMatchingUlps(input, relativeDifference, out min, out max);
            Assert.AreEqual(expectedMin, min);
            Assert.AreEqual(expectedMax, max);
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EqualsWithToleranceWithToleranceLessThanOne()
        {
            Precision.EqualsWithTolerance(1, 2, 0);
        }

        [Test]
        public void EqualsWithTolerance()
        {
            // compare zero and negative zero
            Assert.IsTrue(Precision.EqualsWithTolerance(0, -0, 1));

            // compare two nearby numbers
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, 1.0 + 3 * mDoublePrecision, 1));
            Assert.IsTrue(Precision.EqualsWithTolerance(1.0, 1.0 + mDoublePrecision, 1));
            Assert.IsTrue(Precision.EqualsWithTolerance(1.0, 1.0 - mDoublePrecision, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, 1.0 - 3 * mDoublePrecision, 1));

            // compare with the two numbers reversed in compare order
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0 + 3 * mDoublePrecision, 1.0, 1));
            Assert.IsTrue(Precision.EqualsWithTolerance(1.0 + mDoublePrecision, 1.0, 1));
            Assert.IsTrue(Precision.EqualsWithTolerance(1.0 - mDoublePrecision, 1.0, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0 - 3 * mDoublePrecision, 1.0, 1));

            // compare two slightly more different numbers
            Assert.IsFalse( Precision.EqualsWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 1));
            Assert.IsTrue(Precision.EqualsWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 10));
            Assert.IsTrue(Precision.EqualsWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 10));
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 1));

            // compare different numbers
            Assert.IsFalse(Precision.EqualsWithTolerance(2.0, 1.0, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, 2.0, 1));

            // compare different numbers with large tolerance
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, 1.0 + 1e5 * mDoublePrecision, 1));
            Assert.IsTrue(Precision.EqualsWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 200000));
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 1));

            // compare inf & inf
            Assert.IsTrue(Precision.EqualsWithTolerance(double.PositiveInfinity, double.PositiveInfinity, 1));
            Assert.IsTrue(Precision.EqualsWithTolerance(double.NegativeInfinity, double.NegativeInfinity, 1));

            // compare -inf and inf
            Assert.IsFalse(Precision.EqualsWithTolerance(double.PositiveInfinity, double.NegativeInfinity, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(double.NegativeInfinity, double.PositiveInfinity, 1));

            // compare inf and non-inf
            Assert.IsFalse(Precision.EqualsWithTolerance(double.PositiveInfinity, 1.0, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, double.PositiveInfinity, 1));

            Assert.IsFalse(Precision.EqualsWithTolerance(double.NegativeInfinity, 1.0, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(1.0, double.NegativeInfinity, 1));

            // compare tiny numbers with opposite signs
            Assert.IsFalse(Precision.EqualsWithTolerance(double.Epsilon, -double.Epsilon, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(-double.Epsilon, double.Epsilon, 1));

            Assert.IsFalse(Precision.EqualsWithTolerance(-2.0, 2.0, 1));
            Assert.IsFalse(Precision.EqualsWithTolerance(2.0, -2.0, 1));
        }
   
        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EqualsWithinDecimalPlacesWithDecimalPlacesLessThanOne()
        {
            Precision.EqualsWithinDecimalPlaces(1, 2, 0);
        }

        [Test]
        public void EqualsWithinDecimalPlaces()
        {
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(1, double.NaN, 2));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(double.NaN, 2, 2));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(double.NaN, double.NaN, 2));

            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(double.NegativeInfinity, 2, 2));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(1, double.NegativeInfinity, 2));

            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(double.PositiveInfinity, 2, 2));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(1, double.PositiveInfinity, 2));

            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, 2));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, 2));

            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, 2));
            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, 2));

            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(1.0, 1.04, 2));
            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(1.0, 0.96, 2));

            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(1.0, 1.06, 2));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(1.0, 0.94, 2));

            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(100.0, 104.00, 2));
            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(100.0, 96.000, 2));

            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(100.0, 106.00, 2));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(100.0, 94.000, 2));

            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(0.0, 4 * mDoublePrecision, 12));
            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(0.0, -4 * mDoublePrecision, 12));
        }

        [Test]
        public void IsEqualWithSmallNumbersAndSmallNumberOfDecimalPlaces()
        {
            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(0.0, 1e-12, 1));
            Assert.IsTrue(Precision.EqualsWithinDecimalPlaces(0.0, -1e-12, 1));

            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(0.0, 1e-12, 13));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(0.0, -1e-12, 13));

            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(0.0, 1, 1));
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(0.0, -1, 1));
        }

        [Test]
        public void IsEqualWithDecimalPlacesWithSignRevert()
        {
            Assert.IsFalse(Precision.EqualsWithinDecimalPlaces(0.5, 0.3, 1));
        }

        [Test]
        public void IsLargerWithTolerance()
        {
            // compare zero and negative zero
            Assert.IsFalse(Precision.IsLargerWithTolerance(0, -0, 1));

            // compare two nearby numbers
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 + 3 * mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 + mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 - mDoublePrecision, 1));
            Assert.IsTrue(Precision.IsLargerWithTolerance(1.0, 1.0 - 3 * mDoublePrecision, 1));

            // compare with the two numbers reversed in compare order
            Assert.IsTrue(Precision.IsLargerWithTolerance(1.0 + 3 * mDoublePrecision, 1.0, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0 + mDoublePrecision, 1.0, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0 - mDoublePrecision, 1.0, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0 - 3 * mDoublePrecision, 1.0, 1));

            // compare two slightly more different numbers
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 10));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 10));
            Assert.IsTrue(Precision.IsLargerWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 1));

            // compare different numbers
            Assert.IsTrue(Precision.IsLargerWithTolerance(2.0, 1.0, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 2.0, 1));

            // compare different numbers with large tolerance
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 + 1e5 * mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 200000));
            Assert.IsTrue(Precision.IsLargerWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 1));

            // compare inf & inf
            Assert.IsFalse(Precision.IsLargerWithTolerance(double.PositiveInfinity, double.PositiveInfinity, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(double.NegativeInfinity, double.NegativeInfinity, 1));

            // compare -inf and inf
            Assert.IsTrue(Precision.IsLargerWithTolerance(double.PositiveInfinity, double.NegativeInfinity, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(double.NegativeInfinity, double.PositiveInfinity, 1));

            // compare inf and non-inf
            Assert.IsTrue(Precision.IsLargerWithTolerance(double.PositiveInfinity, 1.0, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(1.0, double.PositiveInfinity, 1));

            Assert.IsFalse(Precision.IsLargerWithTolerance(double.NegativeInfinity, 1.0, 1));
            Assert.IsTrue(Precision.IsLargerWithTolerance(1.0, double.NegativeInfinity, 1));

            // compare tiny numbers with opposite signs
            Assert.IsTrue(Precision.IsLargerWithTolerance(double.Epsilon, -double.Epsilon, 1));
            Assert.IsFalse(Precision.IsLargerWithTolerance(-double.Epsilon, double.Epsilon, 1));   
        }

        [Test]
        public void IsLargerWithDecimalPlaces()
        {
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1, double.NaN, 2));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NaN, 2, 2));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NaN, double.NaN, 2));

            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NegativeInfinity, 2, 2));
            Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(1, double.NegativeInfinity, 2));

            Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(double.PositiveInfinity, 2, 2));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1, double.PositiveInfinity, 2));

            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, 2));
            Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, 2));

            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, 2));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, 2));

            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1.0, 1.04, 2));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1.0, 0.96, 2));
            
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(1.0, 1.06, 2));
            Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(1.0, 0.94, 2));

            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(100.0, 104.00, 2));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(100.0, 96.000, 2));

            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(100.0, 106.00, 2));
            Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(100.0, 94.000, 2));

            double max = 4 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision)));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision)));

            max = 6 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision));
            Assert.IsFalse(Precision.IsLargerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision)));
            Assert.IsTrue(Precision.IsLargerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision)));
        }

        [Test]
        public void IsSmallerWithTolerance()
        {
            // compare zero and negative zero
            Assert.IsFalse(Precision.IsSmallerWithTolerance(0, -0, 1));

            // compare two nearby numbers
            Assert.IsTrue(Precision.IsSmallerWithTolerance(1.0, 1.0 + 3 * mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 + mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 - mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 - 3 * mDoublePrecision, 1));

            // compare with the two numbers reversed in compare order
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0 + 3 * mDoublePrecision, 1.0, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0 + mDoublePrecision, 1.0, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0 - mDoublePrecision, 1.0, 1));
            Assert.IsTrue(Precision.IsSmallerWithTolerance(1.0 - 3 * mDoublePrecision, 1.0, 1));

            // compare two slightly more different numbers
            Assert.IsTrue(Precision.IsSmallerWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 10));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 10));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 1));

            // compare different numbers
            Assert.IsFalse(Precision.IsSmallerWithTolerance(2.0, 1.0, 1));
            Assert.IsTrue(Precision.IsSmallerWithTolerance(1.0, 2.0, 1));

            // compare different numbers with large tolerance
            Assert.IsTrue(Precision.IsSmallerWithTolerance(1.0, 1.0 + 1e5 * mDoublePrecision, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 200000));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 1));

            // compare inf & inf
            Assert.IsFalse(Precision.IsSmallerWithTolerance(double.PositiveInfinity, double.PositiveInfinity, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(double.NegativeInfinity, double.NegativeInfinity, 1));

            // compare -inf and inf
            Assert.IsFalse(Precision.IsSmallerWithTolerance(double.PositiveInfinity, double.NegativeInfinity, 1));
            Assert.IsTrue(Precision.IsSmallerWithTolerance(double.NegativeInfinity, double.PositiveInfinity, 1));

            // compare inf and non-inf
            Assert.IsFalse(Precision.IsSmallerWithTolerance(double.PositiveInfinity, 1.0, 1));
            Assert.IsTrue(Precision.IsSmallerWithTolerance(1.0, double.PositiveInfinity, 1));

            Assert.IsTrue(Precision.IsSmallerWithTolerance(double.NegativeInfinity, 1.0, 1));
            Assert.IsFalse(Precision.IsSmallerWithTolerance(1.0, double.NegativeInfinity, 1));

            // compare tiny numbers with opposite signs
            Assert.IsFalse(Precision.IsSmallerWithTolerance(double.Epsilon, -double.Epsilon, 1));
            Assert.IsTrue(Precision.IsSmallerWithTolerance(-double.Epsilon, double.Epsilon, 1));
        }

        [Test]
        public void IsSmallerWithDecimalPlaces()
        {
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1, double.NaN, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.NaN, 2, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.NaN, double.NaN, 2));

            Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(double.NegativeInfinity, 2, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1, double.NegativeInfinity, 2));

            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.PositiveInfinity, 2, 2));
            Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(1, double.PositiveInfinity, 2));

            Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, 2));

            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, 2));

            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1.0, 1.04, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1.0, 0.96, 2));

            Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(1.0, 1.06, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(1.0, 0.94, 2));

            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(100.0, 104.00, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(100.0, 96.000, 2));

            Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(100.0, 106.00, 2));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(100.0, 94.000, 2));

            double max = 4 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision)));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision)));

            max = 6 * System.Math.Pow(10, Precision.Magnitude(mDoublePrecision));
            Assert.IsTrue(Precision.IsSmallerWithDecimalPlaces(0.0, max, -Precision.Magnitude(mDoublePrecision)));
            Assert.IsFalse(Precision.IsSmallerWithDecimalPlaces(0.0, -max, -Precision.Magnitude(mDoublePrecision)));
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CompareWithToleranceWithUlpsWithNegativeUlps()
        {
            double value = 10.0;
            Precision.CompareWithTolerance(value, value, -1);
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CompareWithToleranceWithUlpsWithZeroUlps()
        {
            double value = 10.0;
            Precision.CompareWithTolerance(value, value, 0);
        }

        [Test]
        public void CompareWithToleranceWithUlpsWithInfinityValue()
        {
            Assert.AreEqual(0, Precision.CompareWithTolerance(double.PositiveInfinity, double.PositiveInfinity, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(double.NegativeInfinity, double.NegativeInfinity, 1));

            Assert.AreEqual(1, Precision.CompareWithTolerance(double.PositiveInfinity, double.NegativeInfinity, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(double.NegativeInfinity, double.PositiveInfinity, 1));

            Assert.AreEqual(-1, Precision.CompareWithTolerance(1, double.PositiveInfinity, 1));
            Assert.AreEqual(1, Precision.CompareWithTolerance(double.PositiveInfinity, 1, 1));

            Assert.AreEqual(1, Precision.CompareWithTolerance(1, double.NegativeInfinity, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(double.NegativeInfinity, 1, 1));
        }

        [Test]
        public void CompareWithToleranceWithUlpsWithNaNValue()
        {
            // compare nan & not-nan
            Assert.AreEqual(1, Precision.CompareWithTolerance(1, double.NaN, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(double.NaN, double.NaN, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(double.NaN, 1, 1));
        }

        // Bug fix test: In the old system comparing -2 and 2 creates a long value that is equal
        // to long.MinValue and you can't get the absolute value of that because long.MinValue is 
        // 1 bigger than long.MaxValue

        [Test]
        public void CompareWithToleranceWithUlpsForValuesThatOverFlowALong()
        {
            Assert.AreEqual(1, Precision.CompareWithTolerance(2.0, -2.0, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(-2.0, 2.0, 1));
        }

        [Test]
        public void CompareWithToleranceWithUlps()
        {
            // compare zero and negative zero
            Assert.AreEqual(0, Precision.CompareWithTolerance(0, -0, 1));

            // compare two nearby numbers
            Assert.AreEqual(-1, Precision.CompareWithTolerance(1.0, 1.0 + 3 * mDoublePrecision, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(1.0, 1.0 + mDoublePrecision, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(1.0, 1.0 - mDoublePrecision, 1));
            Assert.AreEqual(1, Precision.CompareWithTolerance(1.0, 1.0 - 3 * mDoublePrecision, 1));

            // compare with the two numbers reversed in compare order
            Assert.AreEqual(1, Precision.CompareWithTolerance(1.0 + 3 * mDoublePrecision, 1.0, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(1.0 + mDoublePrecision, 1.0, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(1.0 - mDoublePrecision, 1.0, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(1.0 - 3 * mDoublePrecision, 1.0, 1));

            // compare two slightly more different numbers
            Assert.AreEqual(-1, Precision.CompareWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(1.0, 1.0 + 10 * mDoublePrecision, 10));
            Assert.AreEqual(0, Precision.CompareWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 10));
            Assert.AreEqual(1, Precision.CompareWithTolerance(1.0, 1.0 - 10 * mDoublePrecision, 1));

            // compare different numbers
            Assert.AreEqual(1, Precision.CompareWithTolerance(2.0, 1.0, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(1.0, 2.0, 1));

            // compare different numbers with large tolerance
            Assert.AreEqual(-1, Precision.CompareWithTolerance(1.0, 1.0 + 1e5 * mDoublePrecision, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 200000));
            Assert.AreEqual(1, Precision.CompareWithTolerance(1.0, 1.0 - 1e5 * mDoublePrecision, 1));

            // compare inf & inf
            Assert.AreEqual(0, Precision.CompareWithTolerance(double.PositiveInfinity, double.PositiveInfinity, 1));
            Assert.AreEqual(0, Precision.CompareWithTolerance(double.NegativeInfinity, double.NegativeInfinity, 1));

            // compare -inf and inf
            Assert.AreEqual(1, Precision.CompareWithTolerance(double.PositiveInfinity, double.NegativeInfinity, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(double.NegativeInfinity, double.PositiveInfinity, 1));

            // compare inf and non-inf
            Assert.AreEqual(1, Precision.CompareWithTolerance(double.PositiveInfinity, 1.0, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(1.0, double.PositiveInfinity, 1));

            Assert.AreEqual(-1, Precision.CompareWithTolerance(double.NegativeInfinity, 1.0, 1));
            Assert.AreEqual(1, Precision.CompareWithTolerance(1.0, double.NegativeInfinity, 1));

            // compare tiny numbers with opposite signs
            Assert.AreEqual(1, Precision.CompareWithTolerance(double.Epsilon, -double.Epsilon, 1));
            Assert.AreEqual(-1, Precision.CompareWithTolerance(-double.Epsilon, double.Epsilon, 1));
        }

        [Test]
        public void CompareWithDecimalPlaces()
        {
            // compare zero and negative zero
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(0, -0, 1));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(0, -0, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(0, -0, Precision.NumberOfDecimalPlacesForFloats));

            // compare two nearby numbers
            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(1.0, 1.0 + 10 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(1.0, 1.0 + mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(1.0, 1.0 - mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(1.0, 1.0 - 10 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));

            // compare with the two numbers reversed in compare order
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(1.0 + 10 * mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(1.0 + mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(1.0 - mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(1.0 - 10 * mDoublePrecision, 1.0, Precision.NumberOfDecimalPlacesForDoubles));

            // compare two slightly more different numbers
            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(1.0, 1.0 + 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(1.0, 1.0 + 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles - 2));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(1.0, 1.0 - 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles - 2));
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(1.0, 1.0 - 50 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));

            // compare different numbers
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(2.0, 1.0, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(1.0, 2.0, Precision.NumberOfDecimalPlacesForDoubles));

            // compare different numbers with large tolerance
            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(1.0, 1.0 + 1e5 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(1.0, 1.0 - 1e5 * mDoublePrecision, 10));
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(1.0, 1.0 - 1e5 * mDoublePrecision, Precision.NumberOfDecimalPlacesForDoubles));

            // compare inf & inf
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(double.PositiveInfinity, double.PositiveInfinity, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(0, Precision.CompareWithDecimalPlaces(double.NegativeInfinity, double.NegativeInfinity, Precision.NumberOfDecimalPlacesForDoubles));

            // compare -inf and inf
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(double.PositiveInfinity, double.NegativeInfinity, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(double.NegativeInfinity, double.PositiveInfinity, Precision.NumberOfDecimalPlacesForDoubles));

            // compare inf and non-inf
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(double.PositiveInfinity, 1.0, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(1.0, double.PositiveInfinity, Precision.NumberOfDecimalPlacesForDoubles));

            Assert.AreEqual(-1, Precision.CompareWithDecimalPlaces(double.NegativeInfinity, 1.0, Precision.NumberOfDecimalPlacesForDoubles));
            Assert.AreEqual(1, Precision.CompareWithDecimalPlaces(1.0, double.NegativeInfinity, Precision.NumberOfDecimalPlacesForDoubles));
        }
    }
}